// Tencent is pleased to support the open source community by making RapidJSON
// available.
//
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All
// rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License
// at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#ifndef RAPIDJSON_INTERNAL_STACK_H_
#define RAPIDJSON_INTERNAL_STACK_H_

#include <cstddef>
#include "../allocators.h"
#include "swap.h"

#if defined(__clang__)
RAPIDJSON_DIAG_PUSH
RAPIDJSON_DIAG_OFF(c++ 98 - compat)
#endif

RAPIDJSON_NAMESPACE_BEGIN
namespace internal {

///////////////////////////////////////////////////////////////////////////////
// Stack

//! A type-unsafe stack for storing different types of data.
/*! \tparam Allocator Allocator for allocating stack memory.
 */
template <typename Allocator>
class Stack {
 public:
  // Optimization note: Do not allocate memory for stack_ in constructor.
  // Do it lazily when first Push() -> Expand() -> Resize().
  Stack(Allocator *allocator, size_t stackCapacity)
      : allocator_(allocator),
        ownAllocator_(0),
        stack_(0),
        stackTop_(0),
        stackEnd_(0),
        initialCapacity_(stackCapacity) {}

#if RAPIDJSON_HAS_CXX11_RVALUE_REFS
  Stack(Stack &&rhs)
      : allocator_(rhs.allocator_),
        ownAllocator_(rhs.ownAllocator_),
        stack_(rhs.stack_),
        stackTop_(rhs.stackTop_),
        stackEnd_(rhs.stackEnd_),
        initialCapacity_(rhs.initialCapacity_) {
    rhs.allocator_ = 0;
    rhs.ownAllocator_ = 0;
    rhs.stack_ = 0;
    rhs.stackTop_ = 0;
    rhs.stackEnd_ = 0;
    rhs.initialCapacity_ = 0;
  }
#endif

  ~Stack() { Destroy(); }

#if RAPIDJSON_HAS_CXX11_RVALUE_REFS
  Stack &operator=(Stack &&rhs) {
    if (&rhs != this) {
      Destroy();

      allocator_ = rhs.allocator_;
      ownAllocator_ = rhs.ownAllocator_;
      stack_ = rhs.stack_;
      stackTop_ = rhs.stackTop_;
      stackEnd_ = rhs.stackEnd_;
      initialCapacity_ = rhs.initialCapacity_;

      rhs.allocator_ = 0;
      rhs.ownAllocator_ = 0;
      rhs.stack_ = 0;
      rhs.stackTop_ = 0;
      rhs.stackEnd_ = 0;
      rhs.initialCapacity_ = 0;
    }
    return *this;
  }
#endif

  void Swap(Stack &rhs) RAPIDJSON_NOEXCEPT {
    internal::Swap(allocator_, rhs.allocator_);
    internal::Swap(ownAllocator_, rhs.ownAllocator_);
    internal::Swap(stack_, rhs.stack_);
    internal::Swap(stackTop_, rhs.stackTop_);
    internal::Swap(stackEnd_, rhs.stackEnd_);
    internal::Swap(initialCapacity_, rhs.initialCapacity_);
  }

  void Clear() { stackTop_ = stack_; }

  void ShrinkToFit() {
    if (Empty()) {
      // If the stack is empty, completely deallocate the memory.
      Allocator::Free(stack_);  // NOLINT (+clang-analyzer-unix.Malloc)
      stack_ = 0;
      stackTop_ = 0;
      stackEnd_ = 0;
    } else
      Resize(GetSize());
  }

  // Optimization note: try to minimize the size of this function for force
  // inline. Expansion is run very infrequently, so it is moved to another
  // (probably non-inline) function.
  template <typename T>
  RAPIDJSON_FORCEINLINE void Reserve(size_t count = 1) {
    // Expand the stack if needed
    if (RAPIDJSON_UNLIKELY(static_cast<std::ptrdiff_t>(sizeof(T) * count) >
                           (stackEnd_ - stackTop_)))
      Expand<T>(count);
  }

  template <typename T>
  RAPIDJSON_FORCEINLINE T *Push(size_t count = 1) {
    Reserve<T>(count);
    return PushUnsafe<T>(count);
  }

  template <typename T>
  RAPIDJSON_FORCEINLINE T *PushUnsafe(size_t count = 1) {
    RAPIDJSON_ASSERT(stackTop_);
    RAPIDJSON_ASSERT(static_cast<std::ptrdiff_t>(sizeof(T) * count) <=
                     (stackEnd_ - stackTop_));
    T *ret = reinterpret_cast<T *>(stackTop_);
    stackTop_ += sizeof(T) * count;
    return ret;
  }

  template <typename T>
  T *Pop(size_t count) {
    RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T));
    stackTop_ -= count * sizeof(T);
    return reinterpret_cast<T *>(stackTop_);
  }

  template <typename T>
  T *Top() {
    RAPIDJSON_ASSERT(GetSize() >= sizeof(T));
    return reinterpret_cast<T *>(stackTop_ - sizeof(T));
  }

  template <typename T>
  const T *Top() const {
    RAPIDJSON_ASSERT(GetSize() >= sizeof(T));
    return reinterpret_cast<T *>(stackTop_ - sizeof(T));
  }

  template <typename T>
  T *End() {
    return reinterpret_cast<T *>(stackTop_);
  }

  template <typename T>
  const T *End() const {
    return reinterpret_cast<T *>(stackTop_);
  }

  template <typename T>
  T *Bottom() {
    return reinterpret_cast<T *>(stack_);
  }

  template <typename T>
  const T *Bottom() const {
    return reinterpret_cast<T *>(stack_);
  }

  bool HasAllocator() const { return allocator_ != 0; }

  Allocator &GetAllocator() {
    RAPIDJSON_ASSERT(allocator_);
    return *allocator_;
  }

  bool Empty() const { return stackTop_ == stack_; }
  size_t GetSize() const { return static_cast<size_t>(stackTop_ - stack_); }
  size_t GetCapacity() const { return static_cast<size_t>(stackEnd_ - stack_); }

 private:
  template <typename T>
  void Expand(size_t count) {
    // Only expand the capacity if the current stack exists. Otherwise just
    // create a stack with initial capacity.
    size_t newCapacity;
    if (stack_ == 0) {
      if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)();
      newCapacity = initialCapacity_;
    } else {
      newCapacity = GetCapacity();
      newCapacity += (newCapacity + 1) / 2;
    }
    size_t newSize = GetSize() + sizeof(T) * count;
    if (newCapacity < newSize) newCapacity = newSize;

    Resize(newCapacity);
  }

  void Resize(size_t newCapacity) {
    const size_t size = GetSize();  // Backup the current size
    stack_ = static_cast<char *>(
        allocator_->Realloc(stack_, GetCapacity(), newCapacity));
    stackTop_ = stack_ + size;
    stackEnd_ = stack_ + newCapacity;
  }

  void Destroy() {
    Allocator::Free(stack_);
    RAPIDJSON_DELETE(ownAllocator_);  // Only delete if it is owned by the stack
  }

  // Prohibit copy constructor & assignment operator.
  Stack(const Stack &);
  Stack &operator=(const Stack &);

  Allocator *allocator_;
  Allocator *ownAllocator_;
  char *stack_;
  char *stackTop_;
  char *stackEnd_;
  size_t initialCapacity_;
};

}  // namespace internal
RAPIDJSON_NAMESPACE_END

#if defined(__clang__)
RAPIDJSON_DIAG_POP
#endif

#endif  // RAPIDJSON_STACK_H_
